package diploma.main;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import diploma.matrika.MatrikaParov;
import diploma.matrika.MatrikaParov.eTipRazdalje;
import diploma.substitucija.Substitucija;

public class SolveCandidat2
{
	
	public MatrikaParov matSifriranaOriginal;
	
	
	MatrikaParov matrikaSlovarja;
	MatrikaParov matrikaSifriranegaBesedila;
	int maxN;
	eTipRazdalje funkcijaRazdalje;
	Substitucija koncnaSubstitucija = null;
	
	public MatrikaParov getMatrikoDešifriranega()
	{
		return matrikaSifriranegaBesedila; 
	}
	
	public double getRazlikaResitveSSlovarjem()
	{
		return matrikaSlovarja.razdalja(matrikaSifriranegaBesedila, funkcijaRazdalje);
	}
	
	public int getMaxN() {
		return maxN;
	} 
	
	public SolveCandidat2(MatrikaParov slovar, MatrikaParov sifriranoBesedilo, eTipRazdalje razdalja, int N)
	{
		this.funkcijaRazdalje = razdalja;
		this.maxN = N;
		this.matrikaSlovarja = slovar;
		this.matrikaSifriranegaBesedila = sifriranoBesedilo;
		stolpciSlovar = new stackedIndex[MatrikaParov.MAX_PAROV];
	}
	
	int mStIteracij = 0;
	public void doSolve()
	{
		koncnaSubstitucija = new Substitucija();
		koncnaSubstitucija.reset();
		mStIteracij = 1;
		
		//Inicializacija algoritma
		korak1();
		//Ponavljamo korak2 če se rešitev izboljša
		// in si zapomnemo kolikokrat smo šli skozi =)
		while(korak2())
			mStIteracij++;
	}
	
	
	
	public Substitucija getSubstitucijoResitve()
	{
		return koncnaSubstitucija;
	}
	
	public int getŠteviloIteracij()
	{
		return mStIteracij;
	}
	
	private void korak1()
	{
		//Seštejemo stolpce in vrstice
		stolpciSlovar[0] = new stackedIndex(0, 0);
		for(int i = 1; i < MatrikaParov.MAX_PAROV; i++)
		{
			//Delovanje algoritmana podlagi seštevkov vrstic
			//stolpciSlovar[i] = new stackedIndex( matrikaSlovarja.sestejVrstico(i), i);
			
			//Delovanje algoritmana podlagi seštevkov stolpcev
			stolpciSlovar[i] = new stackedIndex( matrikaSlovarja.sestejStolpec(i), i);
		}
		
		//Definiramo komperator, ki pomaga sortitati polje stolpciSlovar
		Comparator<stackedIndex> comperator = new Comparator<SolveCandidat2.stackedIndex>() 
		{
			
			public int compare(stackedIndex o1, stackedIndex o2)
			{
				
				return o1.value > o2.value ? 1 : o1.value <  o2.value ? -1 : 0;
			}
		};
		
		//Sortiramo stolpce po moči, najmanjši je 1. element, največji je zadnji element 
		Arrays.sort(stolpciSlovar, comperator);//list stolpcev z komperatorjem.
	
	}
	
	boolean tabelaPregledanih[] = new boolean[MatrikaParov.MAX_PAROV];
	
	private boolean korak2()
	{
		double razdalja1 = getRazlikaResitveSSlovarjem();
		//NAredimo stack indeksov kandidatov za zamenjavo
		//Resetiramo tabelo pregledanih
		for(int i = 0; i< MatrikaParov.MAX_PAROV; i++)
		{
			tabelaPregledanih[i] = false;
		}
		boolean ostaniVZanki = true;
		for(int idxStack =  MatrikaParov.MAX_PAROV - 1; idxStack >0 && ostaniVZanki; idxStack--)
		{
			//Za vsako iteracija vzame index maksimalnega stolpca iz slovarja
			Integer idxSlovar = stolpciSlovar[idxStack].index;			
			List<Integer> lstMaxN = poisciMaxN(tabelaPregledanih, maxN);
			//Poiščemo stolpec, ki je najbolj primeren(najbolj zmanjša razliko)
			int idxSif = poiščiNajbljižjega(lstMaxN, idxSlovar);

			tabelaPregledanih[idxSlovar] = true;
			
			//Pomemba je kakšna je razdalja pred permutacijo!
			double razdaljaPrej = getRazlikaResitveSSlovarjem();
			matrikaSifriranegaBesedila.permutacija(idxSlovar, idxSif); //Zamenjamo tiste, ki mislomo da so tapravi
			double razdaljaKasneje = getRazlikaResitveSSlovarjem();
			if(razdaljaKasneje > razdaljaPrej)
			{
				//Naredimo undo permutacije
				matrikaSifriranegaBesedila.permutacija(idxSlovar, idxSif);	
				//in gremo ven iz zanke
				ostaniVZanki = false;
			} else
			{
				//Permutacija je bila uspešna, je zmanjšala razdaljo, torej jo zapišemo v 
				//Substitucijsko kodo
				//Uporabimo na koncu koliko parov smo zgrešili
				koncnaSubstitucija.narediZamenjavo(idxSlovar, idxSif);
			}
		}
		//Naredi primerjavo trenutne razlike rešitve s slovarjem 
		double razdalja2 = getRazlikaResitveSSlovarjem();
		//Če je razlika se zmanjšala od računanja razdalja1 pomeni da je ta iteracija algoritma vsaj nekaj naredila! 
		boolean jeBoljša = razdalja2 < razdalja1;
		return jeBoljša;
	}

	private int poiščiNajbljižjega(List<Integer> lstMaxN, int sKaterim)
	{
		if(lstMaxN.size() == 0)
			throw new UnsupportedOperationException("To stanje algoritma ni dovoljeno!");
		
		double najRazdalja = -1;
		int idx  = -1;
		for(Integer i : lstMaxN)
		{
			double raz = racunajRazdaljomed(i, sKaterim);
			if(idx == -1)
			{
				najRazdalja = raz;
				idx = i;
			}
			else if(najRazdalja > raz )
			{
				idx = i;
				najRazdalja = raz;
			}
		}
		return idx;
	}
	
	private double racunajRazdaljomed(int indexSifStolpca, int indexSlovarStolpca)
	{
		double raz;
		matrikaSifriranegaBesedila.permutacija(indexSifStolpca, indexSlovarStolpca);
		raz = matrikaSifriranegaBesedila.razdalja(matrikaSlovarja, funkcijaRazdalje);
		matrikaSifriranegaBesedila.permutacija(indexSifStolpca, indexSlovarStolpca);
		return raz;
	}
	
	List<Integer> lstPregledani = new ArrayList<Integer>();
	
	private List<Integer> poisciMaxN(boolean[] tabelaPregledanih, int N)
	{
		lstPregledani.clear();
		for(int i = 0; i < N; i++)
		{
			int indexMaksimalnega = poisciMax(tabelaPregledanih);
			if(indexMaksimalnega != -1)//da gre ven ko so sam še3je npr
			{
				lstPregledani.add(indexMaksimalnega);
				tabelaPregledanih[indexMaksimalnega] = true;
			}
		}
		
		//Preobrnimo nazaj seznam
		for(Integer i : lstPregledani)
		{
			tabelaPregledanih[i] = false;
		}
		return lstPregledani;
	}
	
	private int poisciMax(boolean[] tabelaPregledanih)
	{
		int indexMax = -1;
		for(int i = 1; i < tabelaPregledanih.length; i++)
		{
			if(!tabelaPregledanih[i])
			{
				if(indexMax == -1)
				{
					indexMax = i;
				}
				else
				{
					if(matrikaSifriranegaBesedila.sestejStolpec(i) > matrikaSifriranegaBesedila.sestejStolpec(indexMax))
					{
						indexMax = i;
					}
				}
			}
		}
		return indexMax;
	}


	stackedIndex stolpciSlovar[];


	class stackedIndex 
	{
		double value;
		int index;
		
		public stackedIndex(double value, int index)
		{
			this.value = value;
			this.index = index;
		}
		
		public String toString()
		{
			return String.format("%s:[%d;%f]",  MatrikaParov.getCharacter(index), index, value);
		}
	}

}

